package de.tum.in.protpred.classification;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.Vector;

import weka.classifiers.Evaluation;
import weka.classifiers.functions.LibSVM;
import weka.core.Instances;
import weka.core.SerializationHelper;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.RemoveType;

public class TMHClassifier {
	private LibSVM classifier;
	private RemoveType filter;
	private String trainingPath;
	private String testPath;
	private Instances testSet;
	private Evaluation evaluation;
	private Instances filteredTestSet;
	
	public TMHClassifier(String trainingPath, String testPath, String[] classifierOptions) throws Exception {
		classifier = new LibSVM();
		this.testSet = DataSource.read(testPath);
		testSet.setClass(testSet.attribute(testSet.numAttributes() - 1));
		filter = new RemoveType();
		String[] filterOptions = {"-T", "string"};
		filter.setOptions(filterOptions);
		filter.setInputFormat(testSet);
		classifier.setOptions(classifierOptions);
		this.trainingPath = trainingPath;
		this.testPath = testPath;
		this.filteredTestSet = filterSet(testPath);
//		System.out.println("Building classifier");
//		this.buildClassifier(trainingPath);
		this.evaluation = createEvaluation(testPath);
		
	}
	

	public void buildClassifier(String trainingPath) {
		try {
			classifier.setProbabilityEstimates(true);
			classifier.buildClassifier(filterSet(trainingPath));
		} catch (Exception e) {
			throw new RuntimeException();
		}
	}
	
	private Instances filterSet(String path) {
		try {
			Instances set = DataSource.read(path);
			Instances result = Filter.useFilter(set, filter);
			result.setClass(result.attribute(result.numAttributes() - 1));
			return result;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private Evaluation createEvaluation(String testPath) {
		try {
			Evaluation eval = new Evaluation(testSet);
			return eval;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public void getDistribution() {
		String[] options = {"-t", trainingPath, "-T", testPath, "-x", "10", "-distribution", "-p", "0"};
		try {
			System.out.println(Evaluation.evaluateModel(classifier, options));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public double[][] getConfusionMatrix()
	{
		return evaluation.confusionMatrix();
	}
	
	public Evaluation getEvaulation()
	{
		return this.evaluation;
	}
	
	public void evaluateModel(String outFile) throws Exception
	{
		StringBuffer buffer = new StringBuffer();
		double[] evalResult = evaluation.evaluateModel(classifier, filteredTestSet, buffer, null, true);	// TODO doesn't this evaluate the whole thing? and then below we do it again for every single instance?
		classifier.setProbabilityEstimates(true);
		File file = new File(outFile);
		if (!file.exists()) {
			file.createNewFile();
		}
		BufferedWriter writer = new BufferedWriter(new FileWriter(file));
		writer.append(buffer.toString());
		writer.close();
	}
	
	public void createOutput(String outputPath) throws Exception {
		StringBuffer buffer = new StringBuffer();
		double[] evalResult = evaluation.evaluateModel(classifier, filteredTestSet, buffer, null, true);	// TODO doesn't this evaluate the whole thing? and then below we do it again for every single instance?
		classifier.setProbabilityEstimates(true);
		File file = new File(outputPath);
		if (!file.exists()) {
			file.createNewFile();
		}
		BufferedWriter writer = new BufferedWriter(new FileWriter(file));
		System.out.println("Evaluating single instances");
		for (int i = 0; i < evalResult.length; i++) {
//			if(i % 500 == 0)
//			{
//				System.out.print("\r" + i + "/" + evalResult.length);
//			}
			classifier.setProbabilityEstimates(true);
			double[] field = classifier.distributionForInstance(filteredTestSet.instance(i));
			String test = testSet.instance(i).stringValue(0) + ": " + filteredTestSet.instance(i).value(filteredTestSet.attribute("class")) + "," + classifier.classifyInstance(filteredTestSet.instance(i)) + "(" + field[0] + ";" + field[1] + ")";
			writer.append(test);
			writer.newLine();
		}
		writer.newLine();
		double[][] matrix = evaluation.confusionMatrix();
		for (int i = 0; i < 2; i++) {
			writer.append(matrix[0][i] + ";" + matrix[1][i]);
			writer.newLine();
		}
		writer.newLine();
		writer.newLine();
		writer.append(buffer.toString());
		writer.close();
	}
	
	public void saveModel(String modelPath) throws Exception {
		Vector<LibSVM> vector = new Vector<LibSVM>();
		vector.add(classifier);
		SerializationHelper.write(modelPath, vector);
	}
	
	@SuppressWarnings("unchecked")
	public void loadModel(String modelPath) {
		try {
			classifier = ((Vector<LibSVM>)SerializationHelper.read(modelPath)).get(0);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}
